﻿using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Input;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
using Sfd.Entities;
using Sfd.Services;
using SfdClient.Messaging;
using SfdClient.Resources;

namespace SfdClient.ViewModel
{
    public class AccountEditorViewModel : WorkingViewModelBase
    {
        private bool _isNewProject;

        private readonly ProjectService _projectService;

        // -----------------CTORS --------------
        public AccountEditorViewModel() : this(new ProjectService(false)) { }

        public AccountEditorViewModel(ProjectService projectService)
        {
            _projectService = projectService;
            Messenger.Default.Register<NavigateToMessage>(
                this,
                message =>
                {
                    if (message.View != ApplicationView.AccountEditorView)
                        return;

                    var project =
                        message.Argument as AzureSyncProject;

                    _isNewProject = project == null;
                    if (project != null)
                    {
                        AccountName = project.AccountName;
                        SharedKey = project.SharedKey;
                        LocalFolder = project.LocalFolder;
                        ContainerName = project.ContainerName;
                        FriendlyName = project.FriendlyName;
                        Id = project.Id;
                        Master = project.Master;
                        Delete = project.Delete;
                        Title = ResourceLocalizer.Current["AccountEditorViewModel_Edition"];
                        // recherche asynchrone de la publication
                        ExecuteCheckIsPublishedCommand();
                    }
                    else
                    {
                        AccountName = string.Empty;
                        SharedKey = string.Empty;
                        LocalFolder = string.Empty;
                        ContainerName = string.Empty;
                        FriendlyName = string.Empty;
                        Id = Guid.NewGuid();
                        Master = SyncMaster.Local;
                        Delete = false;
                        Title = ResourceLocalizer.Current["AccountEditorViewModel_Creation"];
                        IsPublished = false;
                    }
                });
        } 
        // -----------------PROPRIETES--------------

        #region Title Property

        private string _title;

        /// <summary>
        /// The <see cref="Title" /> property's name.
        /// </summary>
        public const string TitlePropertyName = "Title";

        public string Title
        {
            get { return _title; }

            private set
            {
                if (_title == value) return;

                _title = value;
                RaisePropertyChanged(TitlePropertyName);
            }
        }

        #endregion  

        #region AccountName Property

        /// <summary>
        /// The <see cref="AccountName" /> property's name.
        /// </summary>
        public const string AccountNamePropertyName = "AccountName";

        private string _accountName;

        public string AccountName
        {
            get { return _accountName; }

            set
            {
                if (_accountName == value) return;

                _accountName = value;
                RaisePropertyChanged(AccountNamePropertyName);
            }
        }

        #endregion

        #region SharedKey Property

        /// <summary>
        /// The <see cref="SharedKey" /> property's name.
        /// </summary>
        public const string SharedKeyPropertyName = "SharedKey";

        private string _sharedKey;

        public string SharedKey
        {
            get { return _sharedKey; }

            set
            {
                if (_sharedKey == value) return;

                _sharedKey = value;
                RaisePropertyChanged(SharedKeyPropertyName);
            }
        }

        #endregion

        #region LocalFolder Property

        /// <summary>
        /// The <see cref="LocalFolder" /> property's name.
        /// </summary>
        public const string LocalFolderPropertyName = "LocalFolder";

        private string _localFolder;

        public string LocalFolder
        {
            get { return _localFolder; }

            set
            {
                if (_localFolder == value) return;

                _localFolder = value;
                RaisePropertyChanged(LocalFolderPropertyName);
            }
        }

        #endregion

        #region ContainerName Property

        /// <summary>
        /// The <see cref="ContainerName" /> property's name.
        /// </summary>
        public const string ContainerNamePropertyName = "ContainerName";

        private string _containerName;

        public string ContainerName
        {
            get { return _containerName; }

            set
            {
                if (_containerName == value) return;

                _containerName = value;
                RaisePropertyChanged(ContainerNamePropertyName);
            }
        }

        #endregion

        #region FriendlyName Property

        /// <summary>
        /// The <see cref="FriendlyName" /> property's name.
        /// </summary>
        public const string FriendlyNamePropertyName = "FriendlyName";

        private string _friendlyName;

        public string FriendlyName
        {
            get { return _friendlyName; }

            set
            {
                if (_friendlyName == value) return;

                _friendlyName = value;
                RaisePropertyChanged(FriendlyNamePropertyName);
            }
        }

        #endregion

        #region Id Property

        /// <summary>
        /// The <see cref="Id" /> property's name.
        /// </summary>
        public const string IdPropertyName = "Id";

        private Guid _id;

        public Guid Id
        {
            get { return _id; }

            private set
            {
                if (_id == value) return;

                _id = value;
                RaisePropertyChanged(IdPropertyName);
            }
        }

        #endregion

        #region Mode Property

        /// <summary>
        /// The <see cref="Master" /> property's name.
        /// </summary>
        public const string ModePropertyName = "Mode";

        private SyncMaster _master;

        public SyncMaster Master
        {
            get { return _master; }

            set
            {
                if (_master == value) return;

                _master = value;
                RaisePropertyChanged(ModePropertyName);
            }
        }

        #endregion

        #region Delete Property

        /// <summary>
        /// The <see cref="Delete" /> property's name.
        /// </summary>
        public const string DeletePropertyName = "Delete";

        private bool _delete;

        public bool Delete
        {
            get { return _delete; }

            set
            {
                if (_delete == value) return;

                _delete = value;
                RaisePropertyChanged(DeletePropertyName);
            }
        }

        #endregion

        #region IsPublished Property

        /// <summary>
        /// The <see cref="IsPublished" /> property's name.
        /// </summary>
        public const string IsPublishedPropertyName = "IsPublished";

        private bool _isPublished;

        public bool IsPublished
        {
            get { return _isPublished; }

            set
            {
                if (_isPublished == value) return;

                _isPublished = value;
                RaisePropertyChanged(IsPublishedPropertyName);
            }
        }

        #endregion

        #region IsCheckingPublication Property

        /// <summary>
        /// The <see cref="IsCheckingPublication" /> property's name.
        /// </summary>
        public const string IsCheckingPublicationPropertyName = "IsCheckingPublication";

        private bool _isCheckingPublication;

        public bool IsCheckingPublication
        {
            get { return _isCheckingPublication; }

            private set
            {
                if (_isCheckingPublication == value) return;

                _isCheckingPublication = value;
                RaisePropertyChanged(IsCheckingPublicationPropertyName);
            }
        }

        #endregion  

        // ----------------COMMANDS----------------

        #region BackCommand

        private ICommand _backCommand;

        public ICommand BackCommand
        {
            get { return _backCommand ?? (_backCommand = new RelayCommand(ExecuteBackCommand, CanExecuteBackCommand)); }
        }

        public void ExecuteBackCommand()
        {
            if (!CanExecuteBackCommand())
                return;

            Messenger.Default.Send(new NavigateToMessage(ApplicationView.AzureProjectsView));
        }

        public bool CanExecuteBackCommand()
        {
            return !_isCreateSyncProjectRunning && !_isTestConnexionRunning && !IsCheckingPublication;
        }

        #endregion

        private bool _isTestConnexionRunning;
        private bool _isCreateSyncProjectRunning;

        #region TestConnexionCommand

        private ICommand _testConnexionCommand;

        public ICommand TestConnexionCommand
        {
            get {
                return _testConnexionCommand ??
                       (_testConnexionCommand =
                        new RelayCommand(ExecuteTestconnexionCommand, CanExecuteTestconnexionCommand));
            }
        }

        public void ExecuteTestconnexionCommand()
        {
            if (!CanExecuteTestconnexionCommand())
                return;

            Trace.WriteLine(ResourceLocalizer.Current["AccountEditorViewModel_TestEnCours"], TraceLevel.Verbose.ToString());
            MarkWorking(ResourceLocalizer.Current["AccountEditorViewModel_TestEnCours"]);
            _isTestConnexionRunning = true;

            Task.Factory
                .StartNew(
                    () =>
                        {
                            var storageAccount = new CloudStorageAccount(new StorageCredentialsAccountAndKey(AccountName, SharedKey),
                                                         true);
                            var blobClient = new CloudBlobClient(storageAccount.BlobEndpoint,
                                                                 storageAccount.Credentials);

                            // on teste que l'on peut créer le container
                            var container = blobClient.GetContainerReference(ContainerName);
                            // et son existence
                            container.FetchAttributes();
                        })
                .ContinueWith(
                    task =>
                        {
                            MarkNotWorking();
                            _isTestConnexionRunning = false;
                            if (task.IsFaulted)
                            {
                                var storageClientException =
                                    task.Exception == null
                                        ? null
                                        : task.Exception.InnerException as StorageClientException;

                                if (storageClientException != null)
                                {
                                    switch (storageClientException.ErrorCode)
                                    {
                                        case StorageErrorCode.ResourceNotFound:
                                            Trace.WriteLine(ResourceLocalizer.Current["AccountEditorViewModel_ConnexionReussie"], TraceLevel.Info.ToString());
                                            Trace.WriteLine(
                                                string.Format(
                                                    ResourceLocalizer.Current["AccountEditorViewModel_ContainerInexistant"],
                                                    ContainerName),
                                               TraceLevel.Warning.ToString());
                                            break;
                                        case StorageErrorCode.BadRequest:
                                            Trace.WriteLine(
                                                string.Format(
                                                    ResourceLocalizer.Current["AccountEditorViewModel_NomContainerInvalide"],
                                                    ContainerName,
                                                    task.Exception.Message),
                                                TraceLevel.Error.ToString());
                                            break;
                                        default:
                                            Trace.WriteLine(
                                                string.Format(ResourceLocalizer.Current["AccountEditorViewModel_ErreurConnexion"], storageClientException.Message), TraceLevel.Info.ToString());
                                            break;
                                    }
                                }
                                else
                                {
                                    Trace.WriteLine(string.Format(ResourceLocalizer.Current["AccountEditorViewModel_ErreurInconnue"], task.Exception != null ? task.Exception.Message : "???"));
                                }
                            }
                            else
                            {
                                Trace.WriteLine(
                                    string.Format(ResourceLocalizer.Current["AccountEditorViewModel_ContainerExistant"], ContainerName),
                                    TraceLevel.Info.ToString());
                            }
                            CommandManager.InvalidateRequerySuggested();
                        }
                    , TaskScheduler.FromCurrentSynchronizationContext());
        }

        public bool CanExecuteTestconnexionCommand()
        {
            return 
                !string.IsNullOrWhiteSpace(AccountName) && 
                !string.IsNullOrWhiteSpace(SharedKey) && 
                !string.IsNullOrWhiteSpace(ContainerName) && 
                !string.IsNullOrWhiteSpace(LocalFolder) && 
                !_isTestConnexionRunning &&
                !_isCreateSyncProjectRunning;
        }

        #endregion

        #region CreateSyncProjectCommand

        private ICommand _createSyncProjectCommand;

        public ICommand CreateSyncProjectCommand
        {
            get
            {
                if (_createSyncProjectCommand == null)
                    _createSyncProjectCommand = new RelayCommand(ExecuteCreateSyncProjectCommand, CanExecuteCreateSyncProjectCommand);
                return _createSyncProjectCommand;
            }
        }

        public void ExecuteCreateSyncProjectCommand()
        {
            if (!CanExecuteCreateSyncProjectCommand())
                return;

            MarkWorking(ResourceLocalizer.Current["AccountEditorViewModel_SynchronisationEnCours"]);
            _isCreateSyncProjectRunning = true;

            var currentProject = new AzureSyncProject
                                     {
                                         AccountName = AccountName,
                                         ContainerName = ContainerName,
                                         Delete = Delete,
                                         FriendlyName = FriendlyName,
                                         Id = Id,
                                         LocalFolder = LocalFolder,
                                         Master = Master,
                                         SharedKey = SharedKey
                                     };

            Task.Factory.StartNew(
                prj =>
                    {
                        var project = (AzureSyncProject) prj;
                        // création, récupération du container
                        var storageAccount = new CloudStorageAccount(new StorageCredentialsAccountAndKey(project.AccountName, project.SharedKey),
                                             true);

                        var cloudBlobClient = storageAccount.CreateCloudBlobClient();
                        for (var i = 1; i <= 3; i++)
                        {
                            var blobContainer = cloudBlobClient.GetContainerReference(project.ContainerName);
                            blobContainer.CreateIfNotExist();

                            var stream = new MemoryStream();
                            try
                            {
                                blobContainer.GetBlobReference("__testblob").UploadFromStream(stream);
                                blobContainer.GetBlobReference("__testblob").Delete();

                                var permissions = blobContainer.GetPermissions();
                                permissions.PublicAccess = BlobContainerPublicAccessType.Container;
                                blobContainer.SetPermissions(permissions);

                                break;
                            }
                            catch (StorageClientException e)
                            {
                                Trace.WriteLine(
                                    string.Format(
                                        ResourceLocalizer.Current["AccountEditorViewModel_ImpossibleCreationEssaiANouveau"],
                                        e.Message), TraceLevel.Warning.ToString());
                               
                                // on attends 10s
                                System.Threading.Thread.Sleep(10000);
                                if (i == 3)
                                {
                                    // On essaye seulement 3 fois
                                    Trace.WriteLine(string.Format(ResourceLocalizer.Current["AccountEditorViewModel_ImpossibleCreation"], e.Message), TraceLevel.Error.ToString());
                                    throw;
                                }
                            }
                        }

                        // publication ?
                        if (IsPublished)
                        {
                            try
                            {
                                _projectService.PublishProject(project);
                            }
                            catch (Exception exception)
                            {
                                Trace.WriteLine(string.Format(ResourceLocalizer.Current["AccountEditorViewModel_ImpossiblePartage"], exception.Message), TraceLevel.Warning.ToString());
                                //throw;
                            }
                        }
                        else
                        {
                            try
                            {
                                _projectService.UnpublishProject(project.Id);
                            }
                            catch (Exception exception)
                            {
                                Trace.WriteLine(string.Format(ResourceLocalizer.Current["AccountEditorViewModel_ImpossibleNonPartage"], exception.Message), TraceLevel.Warning.ToString());
                                //throw; ;
                            }
                        }
                        
                    }, currentProject)
                .ContinueWith(
                    task =>
                        {
                            MarkNotWorking();
                            _isCreateSyncProjectRunning = false;
                            if (task.IsFaulted)
                            {
                                Trace.WriteLine(string.Format(ResourceLocalizer.Current["AccountEditorViewModel_ErreurCreation"], task.Exception.Message), TraceLevel.Error.ToString());
                            }
                            else
                            {
                                // création du projet puisque la création sur Azure s'est bien déroulé
                                var project = new AzureSyncProject
                                                               {
                                                                   FriendlyName = FriendlyName,
                                                                   AccountName = AccountName,
                                                                   LocalFolder = LocalFolder,
                                                                   ContainerName = ContainerName,
                                                                   SharedKey = SharedKey,
                                                                   Id = Id,
                                                                   Master = Master,
                                                                   Delete = Delete
                                                               };

                                // notification de l'ajout du projet (récupéré dans MainWindowViewModel
                                Messenger.Default.Send(new SyncProjectCollectionChangedMessage(this){Action = _isNewProject ? CollectionChangedAction.Add : CollectionChangedAction.Update, Project = project});
                                Trace.WriteLine(ResourceLocalizer.Current["AccountEditorViewModel_CreationEffectuee"], TraceLevel.Info.ToString());
                                // demande de fermeture de la fenêtre
                                Messenger.Default.Send(new NavigateToMessage(ApplicationView.AzureProjectsView));  
                            }

                            CommandManager.InvalidateRequerySuggested();
                            
                        }, TaskScheduler.FromCurrentSynchronizationContext());
            
        }

        public bool CanExecuteCreateSyncProjectCommand()
        {
            return
                !IsWorking &&
                !string.IsNullOrWhiteSpace(AccountName) &&
                !string.IsNullOrWhiteSpace(SharedKey) &&
                !string.IsNullOrWhiteSpace(ContainerName) &&
                !string.IsNullOrWhiteSpace(LocalFolder) &&
                Directory.Exists(LocalFolder) &&
                !_isCreateSyncProjectRunning &&
                !IsCheckingPublication && !_isTestConnexionRunning;
        }

        #endregion

        #region SelectFolderCommand

        private ICommand _selectFolderCommand;

        public ICommand SelectFolderCommand
        {
            get {
                return _selectFolderCommand ??
                       (_selectFolderCommand =
                        new RelayCommand(ExecuteSelectFolderCommand, CanExecuteSelectFolderCommand));
            }
        }

        public void ExecuteSelectFolderCommand()
        {
            if (!CanExecuteSelectFolderCommand())
                return;

            var message = new SelectFolderMessage(this, ResourceLocalizer.Current["AccountEditorViewModel_SelectionnezDossier"],
                folder =>
                    {
                        LocalFolder = folder;
                    });

            Messenger.Default.Send(message);
        }

        public bool CanExecuteSelectFolderCommand()
        {
            return true;
        }

        #endregion

        #region CheckIsPublishedCommand

        private ICommand _checkIsPublishedCommand;

        public ICommand CheckIsPublishedCommand
        {
            get { return _checkIsPublishedCommand ?? (_checkIsPublishedCommand = new RelayCommand(ExecuteCheckIsPublishedCommand, CanExecuteCheckIsPublishedCommand)); }
        }

        /// <summary>
        /// Check auprès de la base de données si le projet est publié
        /// </summary>
        public void ExecuteCheckIsPublishedCommand()
        {
            if (!CanExecuteCheckIsPublishedCommand())
                return;

            IsCheckingPublication = true;
            Task<bool>.Factory.StartNew(
                () => new ProjectService(false).IsPublished(Id))
                .ContinueWith(task =>
                                  {
                                      IsCheckingPublication = false;
                                      if (task.IsFaulted)
                                      {
                                          var msg = task.Exception.InnerExceptions.Count > 0
                                                        ? task.Exception.InnerExceptions[0].Message : task.Exception.Message;
                                          Trace.WriteLine(string.Format(ResourceLocalizer.Current["AccountEditorViewModel_ErreurConnexionSqlAzure"], msg), TraceLevel.Error.ToString());
                                      }
                                      else
                                      {
                                          IsPublished = task.Result;
                                      }

                                      CommandManager.InvalidateRequerySuggested();
                                  },
                              TaskScheduler.FromCurrentSynchronizationContext());
        }

        public bool CanExecuteCheckIsPublishedCommand()
        {
            return !IsCheckingPublication;
        }

        #endregion

    }
}
